#include <iostream>

#include "DefUniversalApp.h"
#include "MxPluginManager.h"

#ifndef WIN32
# include <limits.h>
# define MAX_PATH PATH_MAX
# include <string.h>
# include <dlfcn.h>
#endif


const char *const MxPluginManager::FnNameCreateClassInstance =
	"CreateClassInstance";
const char *const MxPluginManager::ErrorCodeList = "30000=UnknownError, "
	"30001=CreateMxPluginLibError, 30002=AddMxPluginLibError, "
	"30003=ExceptionError, 30004=InvalidParams, 30005=NoMxPluginsForIID, "
	"30006=InvalidRegKey";
const wchar_t *const MxPluginManager::wErrorCodeList = L"30000=UnknownError, "
	"30001=CreateMxPluginLibError, 30002=AddMxPluginLibError, "
	"30003=ExceptionError, 30004=InvalidParams, 30005=NoMxPluginsForIID, "
	"30006=InvalidRegKey";

MxPluginManager::MxPluginManager(void)
{
}

MxPluginManager::~MxPluginManager(void)
{
	ClearAllMxPlugins();
}

DWORD MxPluginManager::RefreshPluginsInSubFolders(
	const wchar_t *PluginRootFolder, const char *IID)
{
	DWORD ErrorCode = MxPluginManager::UnknownError;

	ClearAllSelections();
	void *hFindFolder = (void *) 0;

	if(
		PluginRootFolder == NULL ||
		wcsnlen(PluginRootFolder, MAX_PATH) == 0 ||
		IID == NULL ||
		strnlen(IID, IMxPluginBaseIID::IIdSize) != IMxPluginBaseIID::IIdSize
	) {
		ErrorCode = MxPluginManager::InvalidParams;
	} else {
		try {
			std::wstring PluginFolder(L"");
			ErrorCode = GetFirstPluginFolderItem(&PluginFolder,
				PluginRootFolder, &hFindFolder, true);
			while(ErrorCode == MxPluginManager::NoError) {
				ErrorCode = RefreshPlugins(PluginFolder.c_str(), IID);
				if(
					ErrorCode != MXPLUGIN_FILE_NOT_FOUND &&
					ErrorCode != MxPluginManager::NoError
				) {
					break;
				}
				ErrorCode = GetNextPluginFolderItem(&PluginFolder,
					PluginRootFolder, &hFindFolder, true);
			}
			if(
				ErrorCode == MXPLUGIN_NO_MORE_FILES ||
				ErrorCode == MXPLUGIN_FILE_NOT_FOUND
			) {
				ErrorCode = MxPluginManager::NoError;
			}
		} catch(...) {
			ErrorCode = MxPluginManager::ExceptionError;
		}

		if(hFindFolder != (void *) 0) {
				// Win32!!
			//::FindClose(hFindFolder);
			std::cerr << "hFindFolder != (void *) 0: Not implemented "
				"on Linux" << std::endl;
		}
		hFindFolder = (void *) 0;

		if(ErrorCode == MxPluginManager::NoError) {
			while(DeleteNextUnselected() == true);
			if(GetPluginCount() == 0)
			{
				ErrorCode = MxPluginManager::NoMxPluginsForIID;
			}
			ClearAllSelections();
		}
	}
	return ErrorCode;
}

DWORD MxPluginManager::RefreshPluginsInFolder(const wchar_t *PluginFolder,
	const char *IID)
{
	DWORD ErrorCode = MxPluginManager::UnknownError;

	ClearAllSelections();

	if(
		PluginFolder == NULL ||
		wcsnlen(PluginFolder, MAX_PATH) == 0 ||
		IID == NULL ||
		strnlen(IID, IMxPluginBaseIID::IIdSize) != IMxPluginBaseIID::IIdSize
	) {
		ErrorCode = MxPluginManager::InvalidParams;
	} else {
		ErrorCode = RefreshPlugins(PluginFolder, IID);
		if(ErrorCode == MxPluginManager::NoError) {
			while(DeleteNextUnselected() == true);
			if(GetPluginCount() == 0) {
				ErrorCode = MxPluginManager::NoMxPluginsForIID;
			}
			ClearAllSelections();
		}
	}
	return ErrorCode;
}

MxPluginLib *MxPluginManager::GetPlugin(const char *LibID)
{
	MxPluginLib *rc = NULL;

	if(LibID != NULL && *LibID != '\0') {
		size_t cnt = _Libs.size();
		if(cnt > 0) {
			for(size_t x = 0; x < cnt; x++) {
				if(
					!strncmp(_Libs[x]->GetLibID(), LibID,
						IMxPluginBaseIID::IIdSize)
				) {
					rc = _Libs[x];
					break;
				}
			}
		}
	}
	return rc;
}

DWORD MxPluginManager::RefreshPlugins(const wchar_t *PluginFolder,
	const char *IID)
{
	DWORD ErrorCode = MxPluginManager::UnknownError;
	HANDLE hFindFile = INVALID_HANDLE_VALUE;

	try {
		std::wstring DllFile(L"");
		ErrorCode = GetFirstPluginFolderItem(&DllFile, PluginFolder, &hFindFile,
			false);
		while(ErrorCode == MxPluginManager::NoError) {
			HINSTANCE hInst = NULL;
#ifdef WIN32
			hInst = ::LoadLibraryEx(DllFile.c_str(), NULL,
				LOAD_LIBRARY_SEARCH_DEFAULT_DIRS);
#else
			hInst = dlopen(DllFile.c_str(), RTLD_NOW);
#endif
			if(hInst == NULL) {
				ErrorCode = GetLastError();
				if (ErrorCode == ERROR_MOD_NOT_FOUND) {
					ErrorCode = MxPluginManager::NoError;
				} else {
					break;
				}
			} else {
				void *(MXSTDMETHOD *pfn)(const char *);
#ifdef WIN32
				*(FARPROC *)&pfn = ::GetProcAddress(hInst,
					MxPluginManager::FnNameCreateClassInstance);
#else
				pfn = (void *(*)(const char *)) dlsym(hInst,
					MxPluginManager::FnNameCreateClassInstance);
#endif
				if(pfn != NULL) {
					IMxPluginLibDetails *tmp =
						static_cast<IMxPluginLibDetails *>(pfn(IID));
					if(tmp != NULL) {
						MxPluginLib *lib = GetPlugin(tmp->GetLibID());
						if(lib == NULL) {
							tmp->DestroyPtr();
						} else {
							tmp->DestroyPtr();
							lib->SetSelection(true);
							::FreeLibrary(hInst);
							hInst = NULL;
						}
						if(hInst != NULL) {
							lib = new MxPluginLib(DllFile.c_str(), hInst, pfn);
							if(lib == NULL) {
								ErrorCode =
									MxPluginManager::CreateMxPluginLibError;
								::FreeLibrary(hInst);
								hInst = NULL;
								break;
							} else {
								lib->SetSelection(true);
								if(
									lib->IsValid() == false ||
									AddMxPlugin(lib) == false
								) {
									ErrorCode =
										MxPluginManager::AddMxPluginLibError;
									delete lib;
									::FreeLibrary(hInst);
									hInst = NULL;
									break;
								}
							}
						}
					}
				}
			}
			ErrorCode = GetNextPluginFolderItem(&DllFile, PluginFolder,
				&hFindFile, false);
		}
		if(hFindFile != INVALID_HANDLE_VALUE) {
			::FindClose(hFindFile);
			hFindFile = INVALID_HANDLE_VALUE;
		}
	} catch(...) {
		ErrorCode = MxPluginManager::ExceptionError;
	}
	if (ErrorCode == ERROR_NO_MORE_FILES) {
		ErrorCode = MxPluginManager::NoError;
	}

	if(hFindFile != INVALID_HANDLE_VALUE) {
		::FindClose(hFindFile);
	}
	hFindFile = INVALID_HANDLE_VALUE;

	return ErrorCode;
}

bool MxPluginManager::AddMxPlugin(MxPluginLib *lib)
{
	bool rc = false;

	if(lib != NULL && lib->IsValid()) {
		if(IsExisting(lib) == false) {
			_Libs.push_back(lib);
			rc = true;
		}
	}
	return rc;
}

bool MxPluginManager::IsExisting(MxPluginLib *lib)
{
	bool rc = false;

	if(lib != NULL) {
		rc = (GetPlugin(lib->GetLibID()) != NULL) ? true : false;
	}

	return rc;
}

void MxPluginManager::ClearAllSelections()
{
	size_t cnt = _Libs.size();

	if(cnt > 0) {
		for(size_t x = 0; x < cnt; x++) {
			_Libs[x]->SetSelection(false);
		}
	}
}

bool MxPluginManager::DeleteNextUnselected()
{
	bool rc = false;
	size_t cnt = _Libs.size();

	if(cnt > 0) {
		for(size_t x = 0; x < cnt; x++) {
			if(_Libs[x]->GetSelection() == false) {
				delete  _Libs[x];
				_Libs[x] = NULL;
				_Libs.erase(_Libs.begin() + x);
				rc = true;
				break;
			}
		}
	}

	return rc;
}

void MxPluginManager::ClearAllMxPlugins()
{
	size_t cnt = _Libs.size();

	if(cnt > 0) {
		for(size_t x = 0; x < cnt; x++) {
			delete _Libs[x];
		}
		_Libs.clear();
	}
}

DWORD MxPluginManager::GetFirstPluginFolderItem(std::wstring *firstFolderResult,
	const wchar_t *PluginFolder, HANDLE *hFindFolder, bool FindFolder)
{
	DWORD rc = MxPluginManager::UnknownError;

	if(
		firstFolderResult != NULL && PluginFolder != NULL &&
		hFindFolder != NULL
	) {
		std::wstring path(PlginFolder);
		int endPathPos = path.find_last_of('\\');
		if(endPathPos != path.length() - 1) {
			path += L"\\";
		}
		std::wstring match(path);
		if(FindFolder == true) {
			match += L"*";
		} else {
			match += L"*.dll";
		}

		try {
			WIN32_FIND_DATA ffd;
			*hFindFolder = ::FindFirstFile(match.c_str(), &ffd);
			if(INVALID_HANDLE_VALUE == *hFindFolder) {
				rc = GetLastError();
			} else {
				bool ItemFound = false;
				do {
					if(FindFolder == true) {
						if(
							ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECOTRY &&
							wcsncmp(ffd.cFileName, L".", 1) != 0 &&
							wcsncmp(ffd.cFileName, L"..", 1) != 0
						) {
							*firstFolderResult = path;
							*firstFolderResult += ffd.cFileName;
							*firstFolderResult += L"\\";
							ItemFound = true;
							break;
						}
					} else {
						if(
							!(ffd.dwFileAttributes &
								FILE_ATTRIBUTE_DIRECTORY) &&
							IsValidLib(ffd.cFileName)
						) {
							*firstFolderResult = path;
							*firstFolderResult += ffd.cFileName;
							ItemFound = true;
							break;
						}
					}
				} while(::FindNextFile(*hFindFolder, &ffd) != 0);
				if(ItemFound == false)
					rc = GetLastError();
				else
					rc = MxPluginManager::NoError;
			}
		} catch(...) {
			rc = GetLastError();
		}

		if(
			*hFindFolder != INVALID_HANDLE_VALUE &&
			rc != MxPluginManager::NoError
		) {
			::FindClose(*hFindFolder);
			*hFindFolder = INVALID_HANDLE_VALUE;
		}
		if(rc != MxPluginManager::NoError)
			*firstFolderResult = L"";
	}

	return rc;
}

DWORD MxPluginManager::GetNextPluginFolderItem(std::wstring *nextFolderResult,
	const wchar_t *PluginFolder, HANDLE *hFindFolder, bool FindFolder)
{
	DWORD rc = MxPluginManager::UnknownError;

	if(
		nextFolderResult != NULL && PluginFolder != NULL &&
		hFindFolder != NULL
	) {
		std::wstring path(PlginFolder);
		int endPathPos = path.find_last_of('\\');
		if(endPathPos != path.length() - 1) {
			path += L"\\";
		}

		try {
			bool ItemFound = false;
			WIN32_FIND_DATA ffd;

			while(::FileNextFile(*hFindFolder, &ffd) != 0) {
				if(FindFolder == true) {
					if(ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECOTRY) {
						*nextFolderResult = path;
						*nextFolderResult += ffd.cFileName;
						ItemFound = true;
						break;
					}
				} else {
					if(
						!(ffd.dwFileAttributes &
							FILE_ATTRIBUTE_DIRECTORY) &&
						IsValidLib(ffd.cFileName)
					) {
						*nextFolderResult = path;
						*nextFolderResult += ffd.cFileName;
						ItemFound = true;
						break;
					}
				}
			}
			if(ItemFound == false)
				rc = GetLastError();
			else
				rc = MxPluginManager::NoError;
		} catch(...) {
			rc = GetLastError();
		}

		if(
			*hFindFolder != INVALID_HANDLE_VALUE &&
			rc != MxPluginManager::NoError
		) {
			::FindClose(*hFindFolder);
			*hFindFolder = INVALID_HANDLE_VALUE;
		}
		if(rc != MxPluginManager::NoError)
			*nextFolderResult = L"";
	}

	return rc;
}

bool MxPluginManager::IsValidLib(const wchar_t *filename)
{
	bool rc = false;

	if(filename != NULL) {
		std::wstring name(filename);
		if(name.find(L"Test.dll") != std::string::npos)
			rc = false;
		else
			rc = true;
	}

	return rc;
}
